﻿using System.Collections.Generic;
using System.Linq;

namespace Language.Parser
{
    internal class Results
    {
        public SortedList<int, Stub.Token> Tokens { get; private set; } = new SortedList<int, Stub.Token>();
        public SortedList<int, Stub.DetailedContext> Stubs { get; private set; } = new SortedList<int, Stub.DetailedContext>();
        public SortedList<int, Stub.Literal> Literals { get; private set; } = new SortedList<int, Stub.Literal>();

        public bool Empty => Tokens.Count == 0 && Stubs.Count == 0 && Literals.Count > 0;
    }

    internal class Local
    {
        #region Private Data

        private Stub.Database _database;
        private string _relativeFilePath;

        #endregion Private Data

        #region Public Properties

        public Results Results { get; private set; }

        #endregion Public Properties

        #region C-Tor

        public Local( Stub.Database database, string relativeFilePath )
        {
            _database = database;
            _relativeFilePath = relativeFilePath;
        }

        #endregion C-Tor

        #region Public Methods

        public void Parse( List<Token> tokens )
        {
            Results = new Results();

            ParseUntilEnd( tokens, 0 );
        }

        #endregion Public Methods

        #region Private Methods

        private void Construct( Stub.DetailedContext context, Token source )
        {
            context.Line = (int)source.Line;
            context.LineStart = (int)source.LineStart;
            context.File = _relativeFilePath;
            context.Position.Start = (int)source.Position;
            context.Position.End = (int)source.End;
        }

        private void AddToken( Token source, Stub.DetailedContext definition )
        {
            Stub.Token token = new Stub.Token();
            Construct( token, source );

            token.Declaration = definition;

            Results.Tokens.Add( token.Position.End, token );
        }

        private void AddComment( Token source )
        {
            Stub.Comment comment = new Stub.Comment();
            Construct( comment, source );

            Results.Stubs.Add( comment.Position.End, comment );
        }

        private void AddLiteral( Token source, Stub.LiteralType type )
        {
            Stub.Literal literal = new Stub.Literal();
            Construct( literal, source );
            literal.Type = type;

            Results.Literals.Add( literal.Position.End, literal );
        }

        private Stub.NameContext FindNamedContext( Stub.Class stub, string name )
        {
            if ( stub == null )
                return null;

            Stub.NameContext result = stub.Functions.FirstOrDefault( f => f.Name == name );

            if ( result != null )
                return result;

            result = stub.Properties.FirstOrDefault( p => p.Name == name );

            if ( result != null )
                return result;

            // Try the parent class
            Stub.DetailedContext parent = _database.ResolveDefinition( stub );
            return FindNamedContext( parent as Stub.Class, name );
        }

        private Stub.NameContext FindNamedContext( Stub.Function stub, string name )
        {
            if ( stub == null )
                return null;

            Stub.NameContext result = stub.Locals.FirstOrDefault( p => p.Name == name );

            if ( result != null )
                return result;

            result = stub.Parameters.FirstOrDefault( p => p.Name == name );
            return result;
        }

        private Stub.NameContext FindNamedContext<T>( T source, string name )
        {
            Stub.NameContext result;

            if ( ( result = FindNamedContext( source as Stub.Function, name ) ) != null )
                return result;

            if ( ( result = FindNamedContext( source as Stub.Class, name ) ) != null )
                return result;

            return null;
        }

        private Stub.Property FindProperty( IEnumerable<Stub.Property> properties, string identifier )
        {
            return properties.FirstOrDefault( property => property.Name == identifier );
        }

        private bool ParseIdentifier( List<Token> tokens, ref int index, Stub.NameContext parent = null )
        {
            if ( index >= tokens.Count )
                return false;

            Token token = tokens[ index ];

            if ( token.Id == 258 /* TOKEN_IDENT */ )
            {
                Stub.File file;
                if( !_database.FileByPath.TryGetValue( _relativeFilePath, out file ) )
                {
                    // The file is new and not in the database yet, so just skip the token and keep going
                    return false;
                }

                Stub.NameContext result = null;

                if ( parent == null )
                {
                    IEnumerable<Stub.IScope> scopes = file.FindScopes( (int)token.Position );

                    foreach ( Stub.IScope scope in scopes )
                    {
                        if ( ( result = FindNamedContext( scope, token.Text ) ) != null )
                            break;
                    }

                }
                else
                {
                    result = FindNamedContext( parent, token.Text );
                }

                ++index;

                AddToken( token, result );

                Stub.DetailedContext resultType = _database.ResolveDefinition( result as Stub.TypedContext );

                if ( !ParseIdentifier( tokens, ref index, resultType as Stub.NameContext ) )
                {
                    --index;
                }

                return true;
            }
            else if ( token.Id == '.' )
            {
                ++index;
                if ( !ParseIdentifier( tokens, ref index, parent ) )
                {
                    --index;
                }

                return true;
            }

            return false;
        }

        private bool ParseComment( List<Token> tokens, ref int index )
        {
            if ( index >= tokens.Count )
                return false;

            Token token = tokens[ index ];

            if ( token.Id == Token.COMMENT_ID )
            {
                AddComment( token );

                return true;
            }

            return false;
        }

        private bool ParseLiteral( List<Token> tokens, ref int index )
        {
            if ( index >= tokens.Count )
                return false;

            Token token = tokens[ index ];

            if( token.Id == 259 /* TOKEN_INTEGER */ )
            {
                AddLiteral( token, Stub.LiteralType.Int );

                return true;
            }

            else if ( token.Id == 260 /* TOKEN_FLOAT */ )
            {
                AddLiteral( token, Stub.LiteralType.Float );

                return true;
            }

            else if ( token.Id == 261 /* TOKEN_NAME */ )
            {
                AddLiteral( token, Stub.LiteralType.Name );

                return true;
            }

            else if ( token.Id == 262 /* TOKEN_STRING */ )
            {
                AddLiteral( token, Stub.LiteralType.String );

                return true;
            }

            return false;
        }

        private bool ParseKeyword( List<Token> tokens, ref int index )
        {
            if ( index >= tokens.Count )
                return false;

            Token token = tokens[ index ];

             if ( token.Id < 263 /* TOKEN_BOOL_TRUE */ )
                return false;

            // Don't include operators as keywords
            if ( token.Id >= 311 /* TOKEN_OP_IADD */ && token.Id <= 322 /* TOKEN_OP_LEEQ */ )
                return false;

            if ( token.Id > 339 /* TOKEN_EVENT */ )
                return false;

            AddLiteral( token, Stub.LiteralType.Keyword );

            return true;
        }

        private bool ParseSubSection( List<Token> tokens, ref int index, uint startId, uint endId )
        {
            if ( index >= tokens.Count )
                return false;

            Token token = tokens[ index ];

            if ( token.Id != startId )
                return false;

            int subSectionStart = index + 1;

            if ( subSectionStart >= tokens.Count )
                return false;

            List<Token> subSection = tokens.GetRange( subSectionStart, tokens.Count - subSectionStart );

            index += ParseUntilEnd( subSection, endId );

            return true;
        }

        private int ParseUntilEnd( List<Token> tokens, uint endDelimiter )
        {
            int i;

            for ( i = 0; i < tokens.Count; ++i )
            {
                Token token = tokens[ i ];

                if ( token.Id == endDelimiter )
                {
                    // Consume the end delimiter token
                    ++i;
                    break;
                }

                if ( ParseSubSection( tokens, ref i, '(', ')' ) )
                    continue;

                if ( ParseSubSection( tokens, ref i, '{', '}' ) )
                    continue;

                if ( ParseSubSection( tokens, ref i, '[', ']' ) )
                    continue;

                if ( ParseIdentifier( tokens, ref i ) )
                    continue;

                if ( ParseComment( tokens, ref i ) )
                    continue;

                if ( ParseKeyword( tokens, ref i ) )
                    continue;

                if ( ParseLiteral( tokens, ref i ) )
                    continue;
            }

            return i;
        }

        #endregion Private Methods
    }
}
